﻿using System;
using System.Linq;
using System.Xml.Linq;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using System.Collections.Generic;

namespace WolfGame {

  /// <summary>
  /// This class will create and populate the data structures needed to render
  /// the map.
  /// </summary>
  class m_MapLoader {

    #region Data Structures

    /// <summary>
    /// Vertex structure containing position, normal, and texture offsets
    /// </summary>
    public struct vertex {
      public Vector3 pos;
      public Vector3 normal;
      public Vector2 tex;

      public vertex(Vector3 _pos, Vector3 _normal, float texx, float texy) {
        pos = _pos;
        normal = _normal;
        tex.X = texx;
        tex.Y = texy;
      }
    }

    /// <summary>
    /// Structure used for collision detection
    /// </summary>
    public struct plane {
      public Vector2 v1;              //greatest and least X value
      public Vector2 v2;              //greatest and least Y value
      public Vector2 v3;              //greatest and least Z value

      public plane(Vector2 _v1, Vector2 _v2, Vector2 _v3) {
        v1 = _v1;
        v2 = _v2;
        v3 = _v3;
      }
    }

    #endregion

    #region Member Variables

    private Common common;
    private float worldTexelSize = .5f;                 //used for texture mapping
    private List<vertex> tempVertices;
    private int surfaceCount;
    private VertexElement[] velements;                  //vertex elements used in vertex decleration

    #endregion

    #region Constructor

    /// <summary>
    /// Instantiate the class
    /// </summary>
    /// <param name="common"></param>
    public m_MapLoader(Common common) {
      this.common = common;
      Console.WriteLine("Loading map...");
      tempVertices = new List<vertex>();
      Surfaces = new List<m_RenderableSurface>();
      Entities = new List<e_EntityBase>();
      Planes = new List<plane>();
      surfaceCount = 0;
      LoadMap();
      createVertexBuffer();
      convertVertexData();
    }

    #endregion

    #region Map Loading

    /// <summary>
    /// Extract the floor components and route them to appropriate methods
    /// </summary>
    private void LoadMap() {
      try {
        string mapPath = System.IO.File.ReadAllText(String.Format(common.MapPath,
          System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location),
          common.Configuration.StartFloorName));

        XDocument map = XDocument.Parse(mapPath);

        // Load all of the Static Walls
        LoadSurfaces(map.Descendants("Walls").ToList());

        // Load all of the floors
        LoadFloors(map.Descendants("Floors").ToList());

        // Load all of the ceilings
        LoadCeilings(map.Descendants("Ceilings").ToList());

        // Load all of the decorations
        LoadDecorations(map.Descendants("Decorations").ToList());

        // Load the player
        LoadPlayer(map.Descendants("Players").ToList());
      }
      catch (Exception ex) {
        Console.WriteLine("Error reading map file.  " + ex.ToString());
      }
    }

    /// <summary>
    /// Load all of the walls in the map
    /// </summary>
    /// <param name="Surfaces"></param>
    private void LoadSurfaces(List<XElement> walls) {
      foreach (XElement wall in walls.DescendantNodes()) {
        m_RenderableSurface temp = new m_RenderableSurface(common);

        LoadCommonSurfaceProperties(temp, wall);

        temp.VertexToStart = surfaceCount * 6;
        LoadSurface(temp.X1, temp.Y1, temp.Z1, 
          temp.X2, temp.Y1, temp.Z2, 
          temp.X2, temp.Y2, temp.Z2, 
          temp.X1, temp.Y2, temp.Z1, false);
      }
    }

    /// <summary>
    /// Load all of the floors in the map
    /// </summary>
    /// <param name="floors"></param>
    private void LoadFloors(List<XElement> floors) {
      foreach (XElement floor in floors.DescendantNodes()) {
        m_RenderableSurface temp = new m_RenderableSurface(common);

        LoadCommonSurfaceProperties(temp, floor);

        LoadSurface(temp.X1, temp.Y1, temp.Z1, 
          temp.X2, temp.Y1, temp.Z1, 
          temp.X2, temp.Y2, temp.Z2, 
          temp.X1, temp.Y2, temp.Z2, false);
      }
    }

    /// <summary>
    /// Load all of the ceilings in the map
    /// </summary>
    /// <param name="ceilings"></param>
    private void LoadCeilings(List<XElement> ceilings) {
      foreach (XElement ceiling in ceilings.DescendantNodes()) {
        m_RenderableSurface temp = new m_RenderableSurface(common);

        LoadCommonSurfaceProperties(temp, ceiling);

        LoadSurface(temp.X1, temp.Y1, temp.Z1,
          temp.X1, temp.Y1, temp.Z2,
          temp.X2, temp.Y2, temp.Z2,
          temp.X2, temp.Y2, temp.Z1, false);
      }
    }

    /// <summary>
    /// Load all of the entities
    /// </summary>
    /// <param name="decorations"></param>
    private void LoadDecorations(List<XElement> decorations) {
      foreach (XElement decoration in decorations.DescendantNodes()) {
        string file = decoration.Attribute("file").Value;
        float x, y, z;
        x = float.Parse(decoration.Attribute("x").Value);
        y = float.Parse(decoration.Attribute("y").Value);
        z = float.Parse(decoration.Attribute("z").Value);

        e_Decoration temp = new e_Decoration(common, file,
          new Vector3(x,y,z));
        temp.Name = decoration.Attribute("name").Value;
        temp.Surface.Name = temp.Name;

        temp.Surface.VertexToStart = surfaceCount * 6;
        Surfaces.Add(temp.Surface);

        LoadSurface(temp.Surface.X1, temp.Surface.Y1, temp.Surface.Z1,
          temp.Surface.X2, temp.Surface.Y1, temp.Surface.Z2,
          temp.Surface.X2, temp.Surface.Y2, temp.Surface.Z2,
          temp.Surface.X1, temp.Surface.Y2, temp.Surface.Z1, true);

        Entities.Add(temp);
      }
    }

    /// <summary>
    /// Load the players
    /// </summary>
    /// <param name="players"></param>
    private void LoadPlayer(List<XElement> players) {
      foreach (XElement player in players.DescendantNodes()) {
        string file = player.Attribute("file").Value;
        float x, y, z;
        x = float.Parse(player.Attribute("x").Value);
        y = float.Parse(player.Attribute("y").Value);
        z = float.Parse(player.Attribute("z").Value);

        e_Player temp = new e_Player(common, file,
          new Vector3(x, y, z));
        temp.Name = player.Attribute("name").Value;
        /*temp.Surface.Name = temp.Name;

        temp.Surface.VertexToStart = surfaceCount * 6;
        Surfaces.Add(temp.Surface);

        LoadSurface(temp.Surface.X1, temp.Surface.Y1, temp.Surface.Z1,
          temp.Surface.X2, temp.Surface.Y1, temp.Surface.Z2,
          temp.Surface.X2, temp.Surface.Y2, temp.Surface.Z2,
          temp.Surface.X1, temp.Surface.Y2, temp.Surface.Z1, true);*/

        Entities.Add(temp);

        common.Player = temp;
        // Assign the camera to the player
        common.Player.Camera = common.Camera;
        common.Camera.setCamera(temp.Position, 0, 0);
        common.Keyboard.CameraOnPlayer = true;
      }
    }

    /// <summary>
    /// Get the common properties used for all renderable surfaces
    /// </summary>
    /// <param name="temp"></param>
    /// <param name="load"></param>
    private void LoadCommonSurfaceProperties(m_RenderableSurface temp, XElement load) {
      temp.X1 = float.Parse(load.Attribute("x1").Value.ToString());
      temp.X2 = float.Parse(load.Attribute("x2").Value.ToString());
      temp.Y1 = float.Parse(load.Attribute("y1").Value.ToString());
      temp.Y2 = float.Parse(load.Attribute("y2").Value.ToString());
      temp.Z1 = float.Parse(load.Attribute("z1").Value.ToString());
      temp.Z2 = float.Parse(load.Attribute("z2").Value.ToString());
      temp.Name = load.Attribute("name").Value.ToString();
      temp.LoadTextures(load.Attribute("texture").Value.ToString());
      temp.VertexToStart = surfaceCount * 6;

      Surfaces.Add(temp);
    }

    /// <summary>
    /// Load the surface and set the texture coordinates
    /// </summary>
    private void LoadSurface(float x1, float y1, float z1,
                             float x2, float y2, float z2,
                             float x3, float y3, float z3,
                             float x4, float y4, float z4,
                             bool entity) {
      //Create the vectors for the four vertices
      Vector3 one = new Vector3(x1, y1, z1);
      Vector3 two = new Vector3(x2, y2, z2);
      Vector3 three = new Vector3(x3, y3, z3);
      Vector3 four = new Vector3(x4, y4, z4);

      //find the largest vertical and horizontal distance between vertices
      float biggestX = 0f;
      float biggestY = 0f;
      float biggestZ = 0f;
      float uVal;
      float vVal;
      float[] tempX = new float[] { x1, x2, x3, x4 };
      float[] tempY = new float[] { y1, y2, y3, y4 };
      float[] tempZ = new float[] { z1, z2, z3, z4 };
      for (int i = 0; i < 3; i++) {
        if (Math.Abs(tempX[0] - tempX[i]) > biggestX)
          biggestX = Math.Abs(tempX[0] - tempX[i]);
      }
      for (int j = 0; j < 4; j++) {
        if (Math.Abs(tempY[0] - tempY[j]) > biggestY)
          biggestY = Math.Abs(tempY[0] - tempY[j]);
      }
      for (int k = 0; k < 4; k++) {
        if (Math.Abs(tempZ[0] - tempZ[k]) > biggestZ)
          biggestZ = Math.Abs(tempZ[0] - tempZ[k]);
      }
      if (biggestX == 0)
        uVal = biggestZ;
      else
        uVal = biggestX;
      if (biggestY == 0)
        vVal = biggestZ;
      else
        vVal = biggestY;
      //next, figure out the size of the texture we are using.
      float texWidth = 96f;
      float texHeight = 128f;

      //now, calculate the U and V coordinates
      float width = texWidth * worldTexelSize;
      float height = texHeight * worldTexelSize;
      float u = uVal / width;
      float v = vVal / height;

      // Entities images are already scaled to their size, so don't do assign
      // the calculated u and v coordinates
      if (entity) {
        u = 1f;
        v = 1f;
      }
      // create a default normal vector
      Vector3 normal = new Vector3(0f, 0f, 1f);

      //Create the triangles
      tempVertices.Add(new vertex(four, normal, 0f, 0f));
      tempVertices.Add(new vertex(one, normal, 0f, v));
      tempVertices.Add(new vertex(two, normal, u, v));
      tempVertices.Add(new vertex(three, normal, u, 0f));
      tempVertices.Add(new vertex(four, normal, 0f, 0f));
      tempVertices.Add(new vertex(two, normal, u, v));

      // Increment the number of surfaces
      surfaceCount++;

      makePlane(one, two, three, four);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="vec1"></param>
    /// <param name="vec2"></param>
    /// <param name="vec3"></param>
    /// <param name="vec4"></param>
    private void makePlane(Vector3 vec1, Vector3 vec2, Vector3 vec3, Vector3 vec4) {
      plane makePlane;

      float[] floats = new float[4];

      floats[0] = vec1.X;
      floats[1] = vec2.X;
      floats[2] = vec3.X;
      floats[3] = vec4.X;
      float bigX = biggestValue(floats);
      float smallX = smallestValue(floats);

      floats[0] = vec1.Y;
      floats[1] = vec2.Y;
      floats[2] = vec3.Y;
      floats[3] = vec4.Y;
      float bigY = biggestValue(floats);
      float smallY = smallestValue(floats);

      floats[0] = vec1.Z;
      floats[1] = vec2.Z;
      floats[2] = vec3.Z;
      floats[3] = vec4.Z;
      float bigZ = biggestValue(floats);
      float smallZ = smallestValue(floats);

      Vector2 x = new Vector2(smallX, bigX);
      Vector2 y = new Vector2(smallY, bigY);
      Vector2 z = new Vector2(smallZ, bigZ);

      makePlane = new plane(x, y, z);
      Planes.Add(makePlane);
    }

    private float biggestValue(float[] floats) {
      float result = -float.MaxValue ;
      for (int i = 0; i < floats.Length; i++) {
        if (floats[i] > result)
          result = floats[i];
      }
      return result;
    }

    private float smallestValue(float[] floats) {
      float result = float.MaxValue;
      for (int i = 0; i < floats.Length; i++) {
        if (floats[i] < result)
          result = floats[i];
      }
      return result;
    }

    #endregion

    #region Buffers

    /// <summary>
    /// Create the vertex buffer and associated vertex definitions for shader use
    /// </summary>
    private void createVertexBuffer() {
      // load the vertices in the correct format
      VertexBuff = new VertexBuffer(typeof(vertex), surfaceCount * 6, common.Render.rDevice, Usage.Dynamic,
          VertexFormats.Position | VertexFormats.Normal | VertexFormats.Texture0, Pool.Default);

      velements = new VertexElement[]
                {
                    new VertexElement(0, 0, DeclarationType.Float3, DeclarationMethod.Default, 
                        DeclarationUsage.Position, 0),
                    new VertexElement(0, 12, DeclarationType.Float3, DeclarationMethod.Default, 
                        DeclarationUsage.Normal, 0),
                    new VertexElement(0, 24, DeclarationType.Float2, DeclarationMethod.Default, 
                        DeclarationUsage.TextureCoordinate, 0),
                    VertexElement.VertexDeclarationEnd
                };
      VertexDec = new VertexDeclaration(common.Render.rDevice, velements);
    }

    /// <summary>
    /// convert the vertex data to the proper format, also convert the planes arraylist to an array
    /// </summary>
    private void convertVertexData() {
      Vertices = (vertex[])tempVertices.ToArray();
      //planesToCheck = (plane[])planes.ToArray(typeof(plane));
      UpdateVertexData();
    }

    /// <summary>
    /// Update all the vertices in the buffer
    /// </summary>
    public void UpdateVertexData() {
      VertexBuff.SetData(Vertices, 0, LockFlags.None);
    }

    #endregion

    #region Properties

    public VertexBuffer VertexBuff { get; set; }
    public VertexDeclaration VertexDec { get; set; }
    public vertex[] Vertices { get; set; }
    public List<m_RenderableSurface> Surfaces { get; set; }
    public List<e_EntityBase> Entities { get; set; }
    public List<plane> Planes { get; set; }

    #endregion
  }
}
